#include <slothttpd/stage/receive_request.hpp>
#include <slothttpd/parser/request_line_parser.hpp>
#include <slothttpd/parser/headers_parser.hpp>
#include <slothttpd/connection.hpp>
#include <slothttpd/logger.hpp>
#include <slothttpd/types.hpp>
#include <slothttpd/server.hpp>
#include <slothttpd/http/header/request.hpp>

#include <cstring>

#include <iostream>
#include <algorithm>

#include <unistd.h>

#include <boost/bind.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>

namespace slothttpd {
namespace stage {

receive_request::receive_request(connection_ptr conn) :
    ref_counted(conn),
    state_(parse_request_line)
{
    if (do_parsing() == parser::parse_again)
        post_read();
}

void receive_request::post_read()
{
    auto free = c->receive_buffers.get_free_space();
    
    if (free.empty()) {
        c->send_stock_response(http::status::request_uri_too_long);
        return;
    }
    
    if (c->receive_buffers.took_new()) {
        switch (state_) {
        case parse_request_line:
            c->send_stock_response(http::status::request_uri_too_long);
            return;
        
        case parse_headers:
            headers_parser_.reallocate_buffer(c->receive_buffers.prev(), c->receive_buffers.current());
            break;
            
        default:
            break;
        }
    }
    
    c->socket.async_read_some(boost::asio::buffer(free.begin(), free.size()),
                              boost::bind(&receive_request::handle_read,
                                          receive_request_ptr(this),
                                          boost::asio::placeholders::error,
                                          boost::asio::placeholders::bytes_transferred));
}

void receive_request::handle_read(receive_request_ptr s,
                                  const boost::system::error_code &err, std::size_t length)
{
    try {
        if (!err) {
            s->c->receive_buffers.provide(length);
            
            if (s->do_parsing() == parser::parse_again) {
                s->post_read();
            }
        } else if (err == boost::asio::error::eof) {
            s->c->send_stock_response(http::status::bad_request);
        } else {
            if (err != boost::asio::error::operation_aborted) {
                logger() << "(" << s.get() << "): " << err.message() << '\n';
            }
            s->c->close();
        }
    } catch (const std::exception &ex) {
        logger() << ex.what() << '\n';
        s->c->send_stock_response(http::status::internal_server_error);
    }
}

parser::result_type receive_request::do_parsing()
{
    parser::result_type r = parser::parse_error;
    
    if (state_ == parse_request_line) {
        r = request_line_parser_.parse(c->receive_buffers);
        
        switch (r) {
        case parser::parse_ok:
            
            c->request.http_version     = request_line_parser_.http_version();
            // TODO: method string to code
            c->request.method_          = request_line_parser_.method();
            c->request.method_name      = request_line_parser_.method_name();
            c->request.schema           = request_line_parser_.schema();
            c->request.host             = request_line_parser_.host();
            c->request.port             = request_line_parser_.port();
            c->request.path             = request_line_parser_.path();
            c->request.path_extension   = request_line_parser_.path_extension();
            c->request.query_string     = request_line_parser_.query_string();
            
            {
                auto &st = c->request.unescape_path();
                if (st != http::status::ok) {
                    c->send_stock_response(st);
                    return parser::parse_error;
                }
            }
            
            state_ = parse_headers;
            break;
            
        case parser::parse_again:
            break;
            
        default: //all errors goes here (Bad method, bad bla-bla...)
            c->send_stock_response(http::status::bad_request);
            return parser::parse_error;
        }
        
    }
    
    if (state_ == parse_headers) {
        try {
            auto callback = boost::bind(&receive_request::header_found, this, _1, _2);
            r = headers_parser_.parse(c->receive_buffers, callback);
        } catch (boost::bad_lexical_cast &ex) {
            logger() << ex.what() << '\n';
            c->send_stock_response(http::status::bad_request);
            
            return parser::parse_error;
        }
        
        switch (r) {
        case parser::parse_ok:
            state_ = parse_done;
            
            if (c->request.content != nullptr) {
                auto content = c->request.content;
                
                // TODO: make max_content_length location dependend
                if (content->length < server::instance->conf->max_content_length) {
                    content->data = static_cast<char*>(c->pool.allocate(content->length));
                    
                    std::size_t  bytes_left = content->length;
                    char        *p = content->data;
                    char_range_t data;
                    
                    while (!(data = c->receive_buffers.get_data()).empty() && bytes_left > 0) {
                        auto consume = std::min(bytes_left, std::size_t(data.size()));
                        
                        p = std::copy(data.begin(), data.begin() + consume, p);
                        
                        bytes_left -= consume;
                        c->receive_buffers.consume(consume);
                    }
                    
                    boost::asio::async_read(c->socket,
                                            boost::asio::buffer(p, bytes_left),
                                            boost::asio::transfer_all(),
                                            boost::bind(&receive_request::handle_read_content,
                                                        receive_request_ptr(this),
                                                        boost::asio::placeholders::error));
                } else {
                    c->send_stock_response(http::status::request_entity_too_large);
                }
            } else {
                server::conf->handle_uri(c);
            }
            break;
            
        case parser::parse_again:
            break;
            
        default: // errors
            c->send_stock_response(http::status::bad_request);
            return parser::parse_error;
        }
    }
    
    return r;
}

void receive_request::handle_read_content(receive_request_ptr s, const boost::system::error_code &err)
{
    if (!err) {
        server::instance->conf->handle_uri(s->c);
    }
}

void receive_request::header_found(char_range_t name, char_range_t value)
{
    if (boost::algorithm::iequals(name, http::headers::request::host)) {
        // if host was specified in the URI, ignore 'Host' header
        // see RFC2616: 5.2 The Resource Identified by a Request
        if (c->request.host.empty()) {
            char *host_start = value.begin();
            char *host_end   = value.end();
            
            char *port_start = nullptr;
            for (char &ch : value) {
                if (ch == ':') {
                    host_end = &ch;
                    port_start = &ch + 1;
                    break;
                }
            }
            
            if (port_start != nullptr) {
                struct isdigit
                {
                    bool operator()(char c) 
                    {
                        return std::isdigit(c);
                    }
                };
                
                char_range_t port(port_start, value.end());
                
                if (boost::algorithm::all(port, isdigit())) {
                    c->request.port = port;
                }
            }
            
            c->request.host = char_range_t(host_start, host_end);
        }
    } else if (boost::algorithm::iequals(name, http::headers::request::content_length)) {
        auto *p = c->request.content = c->pool.construct<http::request::content_t>();
        
        p->header_value = value;
        p->length = boost::lexical_cast<std::size_t>(value);
    } else if (boost::algorithm::iequals(name, http::headers::request::connection)) {
        c->request.keep_alive = boost::algorithm::iequals(value, http::headers::request::keep_alive);
    } else if (boost::algorithm::iequals(name, http::headers::request::content_type)) {
        c->request.content_type = value;
    }
    
    c->request.headers.push_back(*c->pool.construct<request_header>(name, value));
}

}
}
